Skip to content

fix: kanban title fallback chain and board visual improvements - #572

Merged
hotlong merged 2 commits into
mainfrom
copilot/fix-title-resolution-logic
Feb 18, 2026
Merged

fix: kanban title fallback chain and board visual improvements#572
hotlong merged 2 commits into
mainfrom
copilot/fix-title-resolution-logic

Conversation

Copilot AI commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

All kanban cards display "Untitled" when data objects use field names like subject, label, or display_name instead of name/title. The title resolver hardcoded 'name' as default and only fell back to item.title.

Title resolution

Replaced single-field lookup with a fallback chain:

const TITLE_FALLBACK_FIELDS = ['name', 'title', 'subject', 'label', 'display_name'];

// Explicit cardTitle from schema still takes priority, then objectDef metadata,
// then walks the fallback chain for first non-empty value
let resolvedTitle = titleField ? item[titleField] : undefined;
if (!resolvedTitle) {
  for (const field of TITLE_FALLBACK_FIELDS) {
    if (item[field]) { resolvedTitle = item[field]; break; }
  }
}

Visual enhancements (KanbanImpl.tsx)

  • Card accentborder-l-4 border-l-primary/40 left border
  • Column header — Count uses Badge variant="secondary" instead of plain text; header bg bg-muted/30
  • Empty state — "No cards" placeholder when column has zero cards
  • Board containerbg-muted/10 rounded-lg background

All changes are Tailwind-only, backward compatible, no DnD breakage. 10 new unit tests for the fallback chain; all 108 kanban tests pass.

Original prompt

🐛 Bug: All cards show "Untitled"

Root cause in ObjectKanban.tsx (line 126-133):

objectstack-ai / objectui / packages / plugin-kanban / src / ObjectKanban.tsx
const finalTitleField = titleField || 'name';

return rawData.map(item => ({
  ...item,
  id: item.id || item._id,
  title: item[finalTitleField] || item.title || 'Untitled',

The title resolution defaults to the 'name' field, but todo_task objects likely use 'subject' or another field as their title. When item['name'] is empty/undefined and item.title doesn't exist in the raw data either, every card falls through to 'Untitled'.

Fix: Expand the fallback chain to try common field names (name → title → subject → label → display_name) and use the first non-empty value found.

🎨 Enhancement: Visual improvements

The current board (as shown in your screenshot) is functionally working but visually bare:

Cards are plain white boxes with just "Untitled" text
No color accents or visual hierarchy
No empty state indicators in columns
Columns lack visual distinction
Improvements:

Card styling — Add subtle left-border accent, better padding, hover effects, and show card metadata (description, badges, assignee)
Column headers — Add color accents, improve count badge visibility
Empty columns — Show a placeholder when a column has zero cards
Board container — Add subtle background for visual separation
The PR has been submitted to objectstack-ai/objectui and will appear shortly. The Copilot coding agent is now working on the implementation following the strict constraints (Tailwind-only, Shadcn components, English-only, backward compatible, no DnD breakage).


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel

vercel Bot commented Feb 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui Ready Ready Preview, Comment Feb 18, 2026 2:04am
objectui-demo Ready Ready Preview, Comment Feb 18, 2026 2:04am
objectui-storybook Ready Ready Preview, Comment Feb 18, 2026 2:04am

Request Review

- Title resolution now tries: name → title → subject → label → display_name
- Cards have left-border accent for visual hierarchy
- Column headers use Badge component for count visibility
- Empty columns show "No cards" placeholder
- Board container has subtle background for visual separation
- Added 10 unit tests for title fallback chain

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix card title fallback logic in ObjectKanban fix: kanban title fallback chain and board visual improvements Feb 18, 2026
Copilot AI requested a review from hotlong February 18, 2026 02:00
@hotlong

hotlong commented Feb 18, 2026

Copy link
Copy Markdown
Collaborator

看板上没有定义标题是哪个阶段呢?是元数据定义的问题吗?spec 有问题吗 @copilot

Copilot AI commented Feb 18, 2026

Copy link
Copy Markdown
Contributor Author

看板上没有定义标题是哪个阶段呢?是元数据定义的问题吗?spec 有问题吗 @copilot

The bug is not in the spec — KanbanSchema.cardTitle is optional by design. The issue was in the runtime fallback logic inside ObjectKanban.tsx (line 126), which is the last stage of title resolution.

The title resolution pipeline has 4 stages:

  1. Schema stageschema.cardTitle (explicit config) ✅ Spec is fine
  2. Metadata stageobjectDef.titleFormat or objectDef.NAME_FIELD_KEY (from dataSource.getObjectSchema()) ✅ Works when metadata provides it
  3. Fallback stage — hardcoded field name ❌ This was the bug — only tried 'name' then 'title'
  4. Default'Untitled'

When both the schema and the metadata don't specify a title field (e.g. todo_task objects without titleFormat or NAME_FIELD_KEY in their definition), stage 3 would only look for item['name'] then item.title. Objects using subject, label, or display_name as their title field all fell through to 'Untitled'.

The fix expands stage 3 to try: name → title → subject → label → display_name, using the first non-empty value found.

@hotlong
hotlong marked this pull request as ready for review February 18, 2026 02:19
Copilot AI review requested due to automatic review settings February 18, 2026 02:19
@hotlong
hotlong merged commit b15ee60 into main Feb 18, 2026
7 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Kanban card title resolution so cards don’t incorrectly fall back to "Untitled" when objects use alternative title fields (e.g. subject, label, display_name), and applies a few Tailwind-only visual improvements to the Kanban board UI.

Changes:

  • Implemented a title fallback field chain (name → title → subject → label → display_name) when a configured cardTitle/titleField is missing/empty.
  • Updated Kanban board visuals: card left accent border, column header styling/count badge, board container background, and an empty-column placeholder.
  • Added a new unit test file intended to cover the title fallback behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
packages/plugin-kanban/src/ObjectKanban.tsx Adds fallback chain logic for deriving card.title from common field names.
packages/plugin-kanban/src/KanbanImpl.tsx Tailwind-only board/card/column visual tweaks and an empty-state placeholder.
packages/plugin-kanban/src/tests/ObjectKanbanTitle.test.tsx Adds tests around title resolution (currently via a locally reimplemented resolver).

>
<div className="space-y-2" role="list" aria-label={`${column.title} cards`}>
{safeCards.length === 0 && (
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground/50">

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The container is declared role="list", but the empty-state placeholder inserted when there are no cards is a plain <div> without role="listitem". For ARIA list semantics, children should be listitems; consider giving the placeholder role="listitem" (or adjusting the roles/structure) to keep the markup valid for assistive tech.

Suggested change
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground/50">
<div
role="listitem"
className="flex flex-col items-center justify-center py-8 text-muted-foreground/50"
>

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +15
import { describe, it, expect, vi } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useMemo } from 'react';

/**
* Tests for the title resolution fallback chain in ObjectKanban.
*

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused imports (vi, renderHook, useMemo) in this test file add noise and trigger @typescript-eslint/no-unused-vars warnings. Please remove them (or use them if intended).

Suggested change
import { describe, it, expect, vi } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useMemo } from 'react';
/**
* Tests for the title resolution fallback chain in ObjectKanban.
*
import { describe, it, expect } from 'vitest';
/**
* Tests for the title resolution fallback chain in ObjectKanban.
*
* Tests for the title resolution fallback chain in ObjectKanban.
*

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +21
* The effectiveData logic tries fields in this order:
* 1. Explicit cardTitle / titleField from schema
* 2. objectDef.titleFormat (e.g. "{subject}")
* 3. objectDef.NAME_FIELD_KEY
* 4. Fallback chain: name → title → subject → label → display_name
* 5. 'Untitled'

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block comment claims the production title resolution order includes objectDef.titleFormat and objectDef.NAME_FIELD_KEY, but the resolveTitle helper below doesn’t implement or test those branches. Either update the comment to match what’s actually covered here, or extend the helper/tests to include the objectDef-based resolution to avoid misleading documentation.

Suggested change
* The effectiveData logic tries fields in this order:
* 1. Explicit cardTitle / titleField from schema
* 2. objectDef.titleFormat (e.g. "{subject}")
* 3. objectDef.NAME_FIELD_KEY
* 4. Fallback chain: name title subject label display_name
* 5. 'Untitled'
* This helper covers the data-driven title resolution used by ObjectKanban
* after any higher-level schema or object definition logic has been applied.
*
* The logic tested here tries fields in this order:
* 1. Explicit cardTitle / titleField from schema
* 2. Fallback chain: name title subject label display_name
* 3. 'Untitled'

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +40
// Extract the title resolution logic from ObjectKanban to test it in isolation
const TITLE_FALLBACK_FIELDS = ['name', 'title', 'subject', 'label', 'display_name'];

function resolveTitle(item: Record<string, any>, titleField?: string): string {
let resolvedTitle = titleField ? item[titleField] : undefined;

if (!resolvedTitle) {
for (const field of TITLE_FALLBACK_FIELDS) {
if (item[field]) {
resolvedTitle = item[field];
break;
}
}
}

return resolvedTitle || 'Untitled';
}

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests re-implement the title resolution logic locally instead of exercising the actual ObjectKanban implementation. That means a future regression in ObjectKanban.tsx could still leave this test suite passing. Consider extracting the resolver (and TITLE_FALLBACK_FIELDS) into a shared exported utility that both ObjectKanban and this test import, or test effectiveData via rendering ObjectKanban with a minimal schema/dataSource setup.

Copilot uses AI. Check for mistakes.
<h3 id={`kanban-col-${column.id}`} className="font-mono text-xs sm:text-sm font-semibold tracking-wider text-primary/90 uppercase truncate">{column.title}</h3>
<div className="flex items-center gap-2">
<span className="font-mono text-xs text-muted-foreground" aria-label={`${safeCards.length} cards${column.limit ? ` of ${column.limit} maximum` : ''}`}>
<Badge variant="secondary" className="text-xs font-mono tabular-nums">

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapping the card count from a <span> with a descriptive aria-label to a <Badge> drops the accessible context (screen readers will likely announce just the number). Add an aria-label to the Badge (e.g., "N cards" / "N of limit maximum") or include visually-hidden text so the meaning is preserved.

Suggested change
<Badge variant="secondary" className="text-xs font-mono tabular-nums">
<Badge
variant="secondary"
className="text-xs font-mono tabular-nums"
aria-label={
column.limit
? `${safeCards.length} of ${column.limit} cards`
: `${safeCards.length} cards`
}
>

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants